using System;
using System.Collections.Generic;
using System.Reflection;

namespace Ciloci.Flee
{

	/// <include file='Resources/DocComments.xml' path='DocComments/ExpressionImports/Class/*' />	
    /// Imports of static members into an expression
	public sealed class ExpressionImports
	{

		private static Dictionary<string, Type> OurBuiltinTypeMap = CreateBuiltinTypeMap();
		private NamespaceImport MyRootImport;
		private TypeImport MyOwnerImport;

		private ExpressionContext MyContext;
		internal ExpressionImports()
		{
			MyRootImport = new NamespaceImport("true");
		}

		private static Dictionary<string, Type> CreateBuiltinTypeMap()
		{
			Dictionary<string, Type> map = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);

			map.Add("boolean", typeof(bool));
			map.Add("byte", typeof(byte));
			map.Add("sbyte", typeof(sbyte));
			map.Add("short", typeof(short));
			map.Add("ushort", typeof(UInt16));
			map.Add("int", typeof(Int32));
			map.Add("uint", typeof(UInt32));
			map.Add("long", typeof(long));
			map.Add("ulong", typeof(ulong));
			map.Add("single", typeof(float));
			map.Add("double", typeof(double));
			map.Add("decimal", typeof(decimal));
			map.Add("char", typeof(char));
			map.Add("object", typeof(object));
			map.Add("string", typeof(string));

			return map;
		}

		#region "Methods - Non public"
		internal void SetContext(ExpressionContext context)
		{
			MyContext = context;
			MyRootImport.SetContext(context);
		}

		internal ExpressionImports Clone()
		{
			ExpressionImports copy = new ExpressionImports();

			copy.MyRootImport = (NamespaceImport)MyRootImport.Clone();
			copy.MyOwnerImport = MyOwnerImport;

			return copy;
		}

		internal void ImportOwner(Type ownerType)
		{
			MyOwnerImport = new TypeImport(ownerType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, false);
			MyOwnerImport.SetContext(MyContext);
		}

		internal bool HasNamespace(string ns)
		{
			NamespaceImport import = MyRootImport.FindImport(ns) as NamespaceImport;
			return (import != null);
		}

		internal NamespaceImport GetImport(string ns)
		{
			if (ns.Length == 0) {
				return MyRootImport;
			}

			NamespaceImport import = MyRootImport.FindImport(ns) as NamespaceImport;

			if (import == null) {
				import = new NamespaceImport(ns);
				MyRootImport.Add(import);
			}

			return import;
		}

		internal MemberInfo[] FindOwnerMembers(string memberName, System.Reflection.MemberTypes memberType)
		{
			return MyOwnerImport.FindMembers(memberName, memberType);
		}

		internal Type FindType(string[] typeNameParts)
		{
			string[] namespaces = new string[typeNameParts.Length - 1];
			string typeName = typeNameParts[typeNameParts.Length - 1];

			System.Array.Copy(typeNameParts, namespaces, namespaces.Length);
			ImportBase currentImport = MyRootImport;

			foreach (string ns in namespaces) {
				currentImport = currentImport.FindImport(ns);
				if (currentImport == null) {
					break; // TODO: might not be correct. Was : Exit For
				}
			}

			if (currentImport == null) {
				return null;
			} else {
				return currentImport.FindType(typeName);
			}
		}

		static internal Type GetBuiltinType(string name)
		{
			Type t = null;

			if (OurBuiltinTypeMap.TryGetValue(name, out t) == true) {
				return t;
			} else {
				return null;
			}
		}
		#endregion

		#region "Methods - Public"
		/// <include file='Resources/DocComments.xml' path='DocComments/ExpressionImports/AddType1/*' />	
		public void AddType(Type t, string ns)
		{
			Utility.AssertNotNull(t, "t");
			Utility.AssertNotNull(ns, "namespace");

			MyContext.AssertTypeIsAccessible(t);

			NamespaceImport import = this.GetImport(ns);
			import.Add(new TypeImport(t, BindingFlags.Public | BindingFlags.Static, false));
		}

		/// <include file='Resources/DocComments.xml' path='DocComments/ExpressionImports/Addtype2/*' />	
		public void AddType(Type t)
		{
			this.AddType(t, string.Empty);
		}

		/// <include file='Resources/DocComments.xml' path='DocComments/ExpressionImports/AddMethod1/*' />	
		public void AddMethod(string methodName, Type t, string ns)
		{
			Utility.AssertNotNull(methodName, "methodName");
			Utility.AssertNotNull(t, "t");
			Utility.AssertNotNull(ns, "namespace");

			MethodInfo mi = t.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase);

			if (mi == null) {
				string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.CouldNotFindPublicStaticMethodOnType, methodName, t.Name);
				throw new ArgumentException(msg);
			}

			this.AddMethod(mi, ns);
		}

		/// <include file='Resources/DocComments.xml' path='DocComments/ExpressionImports/AddMethod2/*' />	
		public void AddMethod(MethodInfo mi, string ns)
		{
			Utility.AssertNotNull(mi, "mi");
			Utility.AssertNotNull(ns, "namespace");

			MyContext.AssertTypeIsAccessible(mi.ReflectedType);

			if (mi.IsStatic == false | mi.IsPublic == false) {
				string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.OnlyPublicStaticMethodsCanBeImported);
				throw new ArgumentException(msg);
			}

			NamespaceImport import = this.GetImport(ns);
			import.Add(new MethodImport(mi));
		}

		/// <include file='Resources/DocComments.xml' path='DocComments/ExpressionImports/ImportBuiltinTypes/*' />	
		public void ImportBuiltinTypes()
		{
			foreach (KeyValuePair<string, Type> pair in OurBuiltinTypeMap) {
				this.AddType(pair.Value, pair.Key);
			}
		}
		#endregion

		#region "Properties - Public"
		/// <include file='Resources/DocComments.xml' path='DocComments/ExpressionImports/RootImport/*' />	
		public NamespaceImport RootImport {
			get { return MyRootImport; }
		}
		#endregion
	}
}
